package fr.louloutes.lolshooter.engine;

import java.util.ArrayList;
import java.util.Random;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.os.SystemClock;
import android.util.Log;
import android.view.SurfaceHolder;
import fr.louloutes.lolshooter.R;
import fr.louloutes.lolshooter.engine.objects.GameObject;
import fr.louloutes.lolshooter.engine.objects.ObjectGroup;
import fr.louloutes.lolshooter.engine.objects.protagonists.Monster;
import fr.louloutes.lolshooter.engine.objects.protagonists.Player;
import fr.louloutes.lolshooter.engine.objects.protagonists.Protagonist;
import fr.louloutes.lolshooter.engine.objects.weapons.Projectile;
import fr.louloutes.lolshooter.engine.sprites.SpriteMonster;
import fr.louloutes.lolshooter.engine.sprites.SpritePlayer;

/**
 * Class GameThread: main class defining interactions between game entities.
 * 
 * @author Louloute
 * 
 */
public class GameThread extends Thread
{
	public static final Random r = new Random();

	SurfaceHolder holder;
	boolean running = true;
	long lastUpdate = 0;
	int targetFPS = 100;
	long maxBetweenUpdate = 1000 / targetFPS;

	public Player player;
	public boolean isFiring = false;
	ObjectGroup<Projectile> friendlyBullets = new ObjectGroup<Projectile>();
	ObjectGroup<Projectile> enemyBullets = new ObjectGroup<Projectile>();
	ObjectGroup<Protagonist> monsters = new ObjectGroup<Protagonist>();

	Canvas c;
	int height = 0;
	int width = 0;

	int stepY = 0;

	Resources res;
	Bitmap background = null;

	/**
	 * Constructor with initializations.
	 * 
	 * @param holder
	 * @param res
	 */
	public GameThread( SurfaceHolder holder, Resources res )
	{
		this.holder = holder;
		this.res = res;
		c = holder.lockCanvas();
		height = c.getHeight();
		width = c.getWidth();
		holder.unlockCanvasAndPost( c );

		SpritePlayer playerSprite = new SpritePlayer( res );

		playerSprite.setCenter( width / 2, height - 70 );
		player = new Player( playerSprite );

		// Init première wave
		initMonstres();

		// Init bitmap pour background
		Bitmap bgUnresized = BitmapFactory.decodeResource( res,
				R.drawable.bkgrnd );
		background = getResizedBitmap( bgUnresized, height, width );

	}

	/**
	 * Resize a bitmap for drawing purpose. Mainly used to stretch background to
	 * actual screen dims.
	 * 
	 * @param bm
	 * @param newHeight
	 * @param newWidth
	 * @return
	 */
	public Bitmap getResizedBitmap( Bitmap bm, int newHeight, int newWidth )
	{
		int width = bm.getWidth();
		int height = bm.getHeight();
		float scaleWidth = ( ( float ) newWidth ) / width;
		float scaleHeight = ( ( float ) newHeight ) / height;
		// create a matrix for the manipulation
		Matrix matrix = new Matrix();
		// resize the bit map
		matrix.postScale( scaleWidth, scaleHeight );
		// recreate the new Bitmap
		Bitmap resizedBitmap = Bitmap.createBitmap( bm, 0, 0, width, height,
				matrix, false );
		return resizedBitmap;
	}

	/**
	 * Init monsters on screen.
	 */
	private void initMonstres()
	{
		for ( int i = 0; i < 5; i++ )
		{
			boolean goLeft = r.nextDouble() > .5;
			SpriteMonster sMonster = new SpriteMonster( res );
			Monster monster = new Monster( sMonster, goLeft );

			monster.setPosition( r.nextInt( width ), r.nextInt( height / 2 ) );

			monsters.add( monster );
		}

	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	public void run()
	{
		lastUpdate = SystemClock.uptimeMillis();
		while ( running )
		{

			long currentTime = SystemClock.uptimeMillis();
			long elapsedTime = currentTime - lastUpdate;
			if ( elapsedTime > maxBetweenUpdate )
			{

				// Init monsters for waves 2 +
				if ( monsters.isEmpty() )
				{
					initMonstres();

				}

				lastUpdate = currentTime;

				// Lock canvas
				c = holder.lockCanvas();
				Rect frameSize = holder.getSurfaceFrame();
				updatePhysics( frameSize, elapsedTime, currentTime );
				doDraw();
				holder.unlockCanvasAndPost( c );
			}
		}

		c = holder.lockCanvas();
		Rect frameSize = holder.getSurfaceFrame();
		if ( player.isDead() )
		{
			drawGameOver();
		} else
		{
			drawSuccess();
		}
		holder.unlockCanvasAndPost( c );

	}

	/**
	 * Update all game physics.
	 * 
	 * @param frameSize
	 * @param elapsedTime
	 * @param currentTime
	 */
	public void updatePhysics( Rect frameSize, long elapsedTime,
			long currentTime )
	{

		synchronized ( friendlyBullets )
		{
			stepY += 3;
			stepY %= height;

			if ( isFiring )
			{
				if ( player.getWeapon().canFire( currentTime ) )
				{
					player.getWeapon().fire( currentTime );
					createFriendlyBullet();
				}
			}

			for ( Protagonist monster : monsters )
			{
				monster.move( elapsedTime, frameSize );
				if ( monster.getWeapon().canFire( currentTime ) )
				{
					monster.getWeapon().fire( currentTime );
					createEnemyBullet( monster );
				}
			}

			ArrayList<GameObject> toRemoveBulletA = new ArrayList<GameObject>();
			ArrayList<GameObject> toRemoveBulletE = new ArrayList<GameObject>();
			ArrayList<GameObject> toRemoveMonster = new ArrayList<GameObject>();

			for ( Projectile bullet : friendlyBullets )
			{
				bullet.move( elapsedTime, null );

				// if outside of screen
				if ( bullet.getSprite().rect.bottom < 0 )
				{
					toRemoveBulletA.add( bullet );
				} else
				{
					boolean bulletRemoved = false;
					for ( GameObject obj : bullet.collide( monsters ) )
					{
						if ( !bulletRemoved )
						{
							toRemoveBulletA.add( bullet );
							bulletRemoved = true;
						}
						Monster monster = ( Monster ) obj;
						monster.decreaseLife( bullet.getWeapon().getDamage() );
						if ( monster.isDead() )
						{
							toRemoveMonster.add( monster );
							player.addToScore( monster.getValue() );
						}
					}
				}
			}

			for ( Projectile bullet : enemyBullets )
			{

				bullet.move( elapsedTime, null );
				if ( bullet.getSprite().rect.top > height )
				{
					toRemoveBulletE.add( bullet );
				} else
				{
					if ( bullet.getSprite().collide( player.getSprite() ) )
					{
						toRemoveBulletE.add( bullet );
						player.decreaseLife( bullet.getWeapon().getDamage() );
						Log.v( "TEST", "Playerlife: " + player.getLife() );
					}
				}
			}

			friendlyBullets.removeAll( toRemoveBulletA );
			enemyBullets.removeAll( toRemoveBulletE );
			monsters.removeAll( toRemoveMonster );
		}

		if ( player.isDead() )
		{
			end();
		}
	}

	/**
	 * Draw game canvas on screen (after physics have been updated).
	 */
	// public void doDraw( Canvas canvas )
	public void doDraw()
	{
		synchronized ( friendlyBullets )
		{
			// Affichage du fond en scrolling vertical
			// upper part of screen
			c.drawBitmap( background, new Rect( 0, height - stepY, width,
					height ), new Rect( 0, 0, width, stepY ), null );
			// lower part of screen
			c.drawBitmap( background, new Rect( 0, 0, width, height - stepY ),
					new Rect( 0, stepY, width, height ), null );

			// affichage du sprite du joueur
			player.getSprite().paint( c );

			// affichage du score
			drawScore();

			// affichage de la vie du joueur
			drawLife();

			// affichage des bullets amies
			drawFriendlyBullets();

			// affichage des bullets des mechants
			drawEnnemyBullets();

			// affichage des monstres
			drawMonsters();
		}
	}

	/**
	 * Draw all monsters.
	 */
	private void drawMonsters()
	{
		for ( Protagonist monster : monsters )
		{
			monster.getSprite().paint( c );
		}
	}

	/**
	 * Draw all ennemy bullets.
	 */
	private void drawEnnemyBullets()
	{
		for ( Projectile bullet : enemyBullets )
		{
			bullet.getSprite().paint( c );
		}

	}

	/**
	 * Draw all friendly bullets.
	 */
	private void drawFriendlyBullets()
	{
		for ( Projectile bullet : friendlyBullets )
		{
			bullet.getSprite().paint( c );
		}
	}

	/**
	 * Draw a string on screen showing player current score.
	 */
	private void drawScore()
	{
		Paint scorePaint = new Paint();
		scorePaint.setColor( Color.RED );
		scorePaint.setTextAlign( Align.LEFT );
		scorePaint.setTextSize( 30 );
		c.drawText( "Score: " + player.getScore(), 10, 30, scorePaint );

	}

	/**
	 * Draw a life bar corresponding to player's current life.
	 */
	private void drawLife()
	{
		Rect lifebarJauge = new Rect( width - 100, 10, ( width - 100 )
				+ ( 90 * player.getLife() ) / player.getMaxLife(), 20 );
		Paint lifebarJaugePaint = new Paint();
		lifebarJaugePaint.setStyle( Paint.Style.FILL_AND_STROKE );
		lifebarJaugePaint.setColor( Color.YELLOW );
		c.drawRect( lifebarJauge, lifebarJaugePaint );

		Rect lifebarRect = new Rect( width - 100, 10, width - 10, 20 );
		Paint lifebarRectPaint = new Paint();
		lifebarRectPaint.setStyle( Paint.Style.STROKE );
		lifebarRectPaint.setColor( Color.RED );
		c.drawRect( lifebarRect, lifebarRectPaint );

	}

	/**
	 * Create a friendly bullet (from player).
	 */
	public void createFriendlyBullet()
	{
		Projectile bullet = player.getWeapon().getProjectile( res );
		bullet.setPosition( ( int ) player.getSprite().rect.centerX(),
				( int ) player.getSprite().rect.top );
		friendlyBullets.add( bullet );
	}

	/**
	 * Creat an ennemy bullet.
	 * 
	 * @param enemy
	 */
	public void createEnemyBullet( Protagonist enemy )
	{
		Projectile bullet = enemy.getWeapon().getProjectile( res );
		bullet.getSprite().rotate( 180 );
		bullet.setPosition( ( int ) enemy.getSprite().rect.centerX(),
				( int ) enemy.getSprite().rect.bottom );
		enemyBullets.add( bullet );
	}

	/**
	 * End the game.
	 */
	public void end()
	{
		running = false;
	}

	/**
	 * Draw a string on screen showing "Game Over".
	 */
	private void drawGameOver()
	{
		Paint goPaint = new Paint();
		goPaint.setColor( Color.RED );
		goPaint.setTextAlign( Align.LEFT );
		goPaint.setTextSize( 90 );
		goPaint.setAntiAlias( true );
		c.drawText( "Game Over", 10, height / 2, goPaint );

	}

	/**
	 * Draw a string on screen showing "Game Over".
	 */
	private void drawSuccess()
	{
		Paint succPaint = new Paint();
		succPaint.setColor( Color.RED );
		succPaint.setTextAlign( Align.LEFT );
		succPaint.setTextSize( 90 );
		c.drawText( "Success ! Score: " + player.getScore(), 10, height / 2,
				succPaint );

	}

}